﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq.Expressions;

namespace Manoso.Data.FluentMap
{
    public interface IFluent<T>
    {
        /// <summary>
        /// Add a new map to the fluent map tree for a property of the current type.
        /// </summary>
        /// <typeparam name="TP">The property type.</typeparam>
        /// <param name="lambda">Lambda expression define an object property of the current type.</param>
        /// <param name="config">The action to config the map.</param>
        /// <returns></returns>
        IFluent<TP> Map<TP>(Expression<Func<T, TP>> lambda, Action<IMap<TP>> config);

        /// <summary>
        /// Add a new map to the fluent map tree for a property of the current type.
        /// </summary>
        /// <typeparam name="TP">The property type.</typeparam>
        /// <param name="lambda">Lambda expression define an array property of the current type.</param>
        /// <param name="config">The action to config the map.</param>
        /// <returns></returns>
        IFluent<TP> Map<TP>(Expression<Func<T, TP[]>> lambda, Action<IMap<TP>> config);

        /// <summary>
        /// Add a new map to the fluent map tree for a property of the current type.
        /// </summary>
        /// <typeparam name="TP">The property type.</typeparam>
        /// <param name="lambda">Lambda expression define an IList property of the current type.</param>
        /// <param name="config">The action to config the map.</param>
        /// <returns></returns>
        IFluent<TP> Map<TP>(Expression<Func<T, IList<TP>>> lambda, Action<IMap<TP>> config);

        /// <summary>
        /// Add a new map to the fluent map tree for a property of the current type.
        /// </summary>
        /// <typeparam name="TP">The property type.</typeparam>
        /// <param name="lambda">Lambda expression define an IEnumerable property of the current type.</param>
        /// <param name="add">Action delegate define the adding of the property object to an object of current type.</param>
        /// <param name="create">Action delegate define how to create an empty property (collection).</param>
        /// <param name="config">The action to config the map.</param>
        /// <returns></returns>
        IFluent<TP> Map<TP>(Expression<Func<T, IEnumerable>> lambda, Action<T, TP> add, Action<T> create, Action<IMap<TP>> config);

        /// <summary>
        /// Cast current map node type to TCast. TCase must be a child type of T.
        /// </summary>
        /// <typeparam name="TCast">Type to cast to.</typeparam>
        /// <param name="predicate">Predicate on when to cast.</param>
        /// <param name="config">The action to config the cast.</param>
        /// <returns></returns>
        IFluent<TCast> As<TCast>(Func<TCast, bool> predicate, Action<IMap<TCast>> config) where TCast : T;
    }

    internal class Fluent<T> : IFluent<T>
    {
        internal IMap<T> MapO { get; set; }

        internal IMap MapI
        {
            get { return MapO as IMap; }
        }

        public IFluent<TP> Map<TP>(Expression<Func<T, TP>> lambda, Action<IMap<TP>> config)
        {
            var relation = new Relation<T, TP>(lambda);
            var map = FluentMap.Map.Create<T, TP>(relation, MapI);
            if (config != null)
                config(map);
            return new Fluent<TP> { MapO = map };
        }

        public IFluent<TP> Map<TP>(Expression<Func<T, TP[]>> lambda, Action<IMap<TP>> config)
        {
            var relation = new Relation<T, TP>(lambda);
            var map = FluentMap.Map.Create<T, TP>(relation, MapI);
            if (config != null)
                config(map);
            return new Fluent<TP> { MapO = map };
        }

        public IFluent<TP> Map<TP>(Expression<Func<T, IList<TP>>> lambda, Action<IMap<TP>> config)
        {
            var relation = new Relation<T, TP>(lambda);
            var map = FluentMap.Map.Create<T, TP>(relation, MapI);
            if (config != null)
                config(map);
            return new Fluent<TP> { MapO = map };
        }

        public IFluent<TP> Map<TP>(Expression<Func<T, IEnumerable>> lambda, Action<T, TP> add, Action<T> create, Action<IMap<TP>> config)
        {
            var relation = new Relation<T, TP>(lambda, add, create);
            var map = FluentMap.Map.Create<T, TP>(relation, MapI);
            if (config != null)
                config(map);
            return new Fluent<TP> { MapO = map };
        }

        public IFluent<TCast> As<TCast>(Func<TCast, bool> predicate, Action<IMap<TCast>> config) where TCast : T
        {
            MapI.Cast<T, TCast>(x => x.Match(predicate));
            var relation = new Relation<T, TCast>();
            var map = FluentMap.Map.Create<T, TCast>(relation, MapI);

            if (config != null)
                config(map);

            return new Fluent<TCast> { MapO = map };
        }
    }

}
